Dieses Notebook bereitet die Daten für die Intelligent Zoning Engine vor. Es speichert

Bereits in anderen scripten wurde vorbereitet:

Die Daten werden wiefolgt vorbereitet:

TODO: - die sozioökonomischen Faktoren werden aus den Wahlbezirken auf die Blöcke hochgerechnet (https://github.com/berlinermorgenpost/cogran)

Laden der Daten

sampled_buildings = read_rds('output/sampled_buildings.rds')
bez = readOGR('download/RBS_OD_BEZ_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_BEZ_2015_12.geojson", layer: "OGRGeoJSON"
with 13 features
It has 2 fields
blk = readOGR('download/RBS_OD_BLK_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_BLK_2015_12.geojson", layer: "OGRGeoJSON"
with 15720 features
It has 4 fields
lor = readOGR('download/RBS_OD_LOR_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_LOR_2015_12.geojson", layer: "OGRGeoJSON"
with 447 features
It has 8 fields
re_schulstand = readOGR('download/re_schulstand.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/re_schulstand.geojson", layer: "OGRGeoJSON"
with 709 features
It has 20 fields

Schulwege

route_matrix = read_rds('output/route_matrix.rds')

Schulkapazitäten und Einwohnerzahler auf LOR-Ebene

kapas = read_csv('download/anmeldezahlen.csv') %>% filter(grepl('G', Schulnummer)) %>% filter(!is.na(`Plätze`))
Parsed with column specification:
cols(
  Bezirk = col_character(),
  Schulnummer = col_character(),
  Schulname = col_character(),
  Plätze = col_integer(),
  Anmeldungen = col_character()
)
einwohner_lor = read_delim('download/EWR201512E_Matrix.csv', delim=';')
Parsed with column specification:
cols(
  .default = col_character(),
  ZEIT = col_integer(),
  STADTRAUM = col_integer(),
  E_E = col_number(),
  E_EM = col_number(),
  E_EW = col_number(),
  E_E50_55 = col_number(),
  E_E25U55 = col_number(),
  E_E55U65 = col_number(),
  E_E65U80 = col_number()
)
See spec(...) for full column specifications.

Überprüfung der Vollständigkeit der Daten über Anmeldezahlen/Kapazitäten

re_schulstand_df = re_schulstand %>% as.data.frame() %>% rename(lon=coords.x1, lat=coords.x2)
re_schulstand_df %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>%
  group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
  rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n()))
Joining, by = "Bezirk"

Für welche Bezirke haben wir für alle Schulen Kapazitäten gegeben?

re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>% group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
  rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n())) %>% filter(`Anzahl Schulen` == `Mit Kapazität`)
Joining, by = "Bezirk"

Überprüfung ob die Liste der Schulen und Liste der Schulen mit Kapazitätsinformationen gleich sind:

bezirk = 'Tempelhof-Schöneberg'
schulen_mit_kapa = kapas %>% filter(Bezirk == bezirk) %>% .$Schulnummer
schulen_mit_kapa
 [1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12"
 [9] "07G13" "07G14" "07G15" "07G16" "07G17" "07G18" "07G19" "07G20"
[17] "07G21" "07G22" "07G23" "07G24" "07G25" "07G26" "07G27" "07G28"
[25] "07G29" "07G30" "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
07G01

07G02

07G03

07G05

07G06

07G07

07G10

07G12

07G13

07G14

07G15

07G16

07G17

07G18

07G19

07G20

07G21

07G22

07G23

07G24

07G25

07G26

07G27

07G28

07G29

07G30

07G31

07G32

07G34

07G35

07G36

07G37
grundschulen = re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK == bezirk) %>% .$spatial_name
'In Anmeldeliste, fehlt in Schulstand'
[1] "In Anmeldeliste, fehlt in Schulstand"
In Anmeldeliste, fehlt in Schulstand
setdiff(schulen_mit_kapa, grundschulen)
character(0)
'In re_schulstand, fehlt in Anmeldeliste'
[1] "In re_schulstand, fehlt in Anmeldeliste"
In re_schulstand, fehlt in Anmeldeliste
setdiff(grundschulen, schulen_mit_kapa)
character(0)
map = get_map('Berlin')
Map from URL : http://maps.googleapis.com/maps/api/staticmap?center=Berlin&zoom=10&size=640x640&scale=2&maptype=terrain&language=en-EN&sensor=false
Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address=Berlin&sensor=false
re_schulstand_df_w_kapas = re_schulstand_df %>% left_join(kapas, by=c('spatial_name'='Schulnummer'))

Plot aller Schulen, mit der Info, ob Kapazitätsinformationen verfügbar sind.

data = re_schulstand_df_w_kapas %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk) %>% mutate(missing.capa=is.na(`Plätze`))
ggmap(map) + geom_point(aes(lon, lat, color=missing.capa), data=data) +
    coord_map(xlim=c(min(data$lon)-0.01, max(data$lon)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Filter auf Schulen mit Kapazitätsinformationen (für T-S sind das alle):

relevant_schools = re_schulstand_df_w_kapas %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk & !is.na(`Plätze`)) %>% .$spatial_name
relevant_schools
 [1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12"
 [9] "07G13" "07G14" "07G15" "07G16" "07G17" "07G18" "07G19" "07G20"
[17] "07G21" "07G22" "07G23" "07G24" "07G25" "07G26" "07G27" "07G28"
[25] "07G29" "07G30" "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
07G01

07G02

07G03

07G05

07G06

07G07

07G10

07G12

07G13

07G14

07G15

07G16

07G17

07G18

07G19

07G20

07G21

07G22

07G23

07G24

07G25

07G26

07G27

07G28

07G29

07G30

07G31

07G32

07G34

07G35

07G36

07G37

Mapping Bezirk->LOR->Block

df_bez = as.data.frame(bez)
df_lor = as.data.frame(lor)
df_blk = as.data.frame(blk)

Sanity-Check: LORs und Blöcke im Bezirk

bez_id = filter(df_bez, BEZNAME == bezirk)$BEZ
relevant_lors = df_lor %>% filter(BEZ == bez_id)
relevant_blks = df_blk %>% filter(BEZ == bez_id)
ggplot() + geom_path(aes(x=long, y=lat, group=group), data=lor[lor$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez, color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Blöcke im Bezirk

ggplot() + geom_path(aes(x=long, y=lat, group=group), data=blk[blk$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez[bez$BEZ == bez_id,], color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Kinder im Bezirk auf Blöcke hochrechnen

Über die Einwohnerinformationen in RBS_OD_BLK_2015_12.geojson kann EWR201512E_Matrix.csv von LOR-Ebene auf Blockebene hochgerechnet werden.

TODO: Stattdessen mit https://github.com/berlinermorgenpost/cogran machen?

Plot der 6-Jährigen nach EWR201512E_Matrix.csv

Wir verwenden das mittel der 5- und 6-Jährigen.

TODO neue Daten von Torres? TODO Prognose?

relevant_ewr = einwohner_lor %>%
  select(RAUMID, E_E05_06, E_E06_07) %>%
  filter(RAUMID %in% relevant_lors$PLR) %>%
  # Schnitt der 5 und 6-Jährigen
  mutate(kids=(as.numeric(gsub(',','.',E_E06_07))+as.numeric(gsub(',','.',E_E05_06)))/2) %>% as.data.frame()

data = tidy(lor[lor$BEZ == bez_id,], region='PLR') %>% inner_join(relevant_ewr, by=c('id'='RAUMID'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Plot der Einwohner auf Blockebene

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(df_blk, by=c('id'='BLK')) %>% mutate(Einw=ifelse(Einw==0, NA, Einw))
0
[1] 0
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=Einw), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Hochrrechnung auf Blöcke, Strukturquote

Strukturquote = 0.9

kids_in_blks = relevant_blks %>%
  group_by(PLR) %>%
  mutate(EinwRatio = Einw/sum(Einw)) %>%
  ungroup %>%
  left_join(relevant_ewr, by=c('PLR'='RAUMID')) %>%
  mutate(kids = EinwRatio*kids) %>%
  mutate(kids = Strukturquote*kids) %>% # Strukturquote
  select(BEZ, PLR, BLK, Einw, kids) %>%
  as.data.frame()
row.names(kids_in_blks) = kids_in_blks$BLK

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(kids_in_blks, by=c('id'='BLK')) %>% mutate(kids=ifelse(kids==0, NA, kids), Einw=ifelse(Einw==0, NA, Einw))

ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Verfügbare Plätze

relevant_kapas = kapas %>% select(Schulnummer, Kapa=`Plätze`) %>% filter(Schulnummer %in% relevant_schools) %>% as.data.frame()
#row.names(relevant_kapas) = relevant_kapas$Schulnummer

Überprüfung der Summe der Kapazitäten, Anmeldungen und Kinderstatistiken

'Summe Kapas'
[1] "Summe Kapas"
Summe Kapas
relevant_kapas %>% .$Kapa %>% sum
[1] 2584
'Anmeldungen'
[1] "Anmeldungen"
Anmeldungen
kapas %>% mutate(Anmeldungen = as.numeric(gsub('[^0-9]', '', Anmeldungen))) %>% filter(Schulnummer %in% relevant_schools) %>% .$Anmeldungen %>% sum
[1] 2752
'Kids laut Statistik'
[1] "Kids laut Statistik"
Kids laut Statistik
kids_in_blks$kids %>% sum
[1] 2620.8
relevant_ewr$kids %>% sum
[1] 2912

Schulwege von Blöcken zu Schulen aggregieren

Für jedes Wohngebäude suchen wir den zugehörigen Block

residential_buildings_blocks = sampled_buildings %>% inner_join(df_blk) %>% filter(BEZ == bez_id)
Joining, by = "BLK"
residential_buildings_blocks
routes_from_blks = residential_buildings_blocks %>%
  left_join(route_matrix %>% filter(dst %in% relevant_schools), by=c('OI'='src'))
head(routes_from_blks)

Plot der relevanten Blöcke (mit Wohngebäuden)

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(routes_from_blks %>% group_by(BLK) %>% summarise(n=n()), by=c('id'='BLK'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group), fill='red', data=data) +
  #geom_point(aes(x=lon, y=lat), data=rb_df, color='black', size=0.01) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_from_blks = routes_from_blks %>% as.data.frame() %>% group_by(BLK, dst) %>% summarise(min=min(distance), avg=mean(distance), med=median(distance), max=max(distance)) %>% ungroup
travel_from_blks

Plot der Blöcke mit Färbung nach durchschnittlichem Weg zur nächsten Schule

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(travel_from_blks %>% group_by(BLK) %>% top_n(1, -avg), by=c('id'='BLK'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=-avg), data=data) +
  geom_point(aes(lon, lat), color='red', data = re_schulstand_df %>% filter(BEZIRK==bezirk & SCHULART=='Grundschule')) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_matrix = travel_from_blks %>% select(BLK, dst, avg) %>% spread(dst, avg)
dim(travel_matrix)
[1] 1012   33
travel_matrix

Sozioökonomische Daten

Wir haben Sozioökonomische Daten in den Wahlbezirken. Strategie: - Schneiden der Wahlbezirke mit den Blöcken - Übernahme des Prozentwertes vom Wahlbezirk für jeden (Unter-)block - Zuordnung zu jedem Block und Vereinigung durch Flächen/Wohnhaus-gewichtetes Mittel des Prozentwertes

UWB = readOGR('download/RBS_OD_UWB_AGH2016.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_UWB_AGH2016.geojson", layer: "OGRGeoJSON"
with 1779 features
It has 4 fields
UWB$ID = paste0(UWB$BEZ, UWB$UWB)
sozio_UWB = read_excel('download/DL_BE_AH2016_Strukturdaten.xlsx', sheet = 3) %>% select(ID, sgbIIu65=`Einwohner unter 65 in SGB II 2014 Prozent`)
UWB = UWB %>% sp::merge(sozio_UWB, by='ID')

ggplot(broom::tidy(UWB, region='ID') %>% inner_join(UWB %>% as.data.frame, by=c('id'='ID'))) + geom_polygon(aes(x=long, y=lat, group=group, fill=sgbIIu65)) + coord_map()

Check for self intersections

wgs84 = CRS(proj4string(blk))
ea_projection = CRS("+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs")
blk_in_bez = blk[blk$BEZ == '07',]
gIsValid(blk_in_bez)
[1] TRUE
blk_in_bez = gBuffer(spTransform(blk_in_bez, ea_projection), byid=T, width = -0.1)
any(xor(gIntersects(blk_in_bez, byid=T, returnDense = T), diag(1, length(blk_in_bez)) == 1))
[1] FALSE
plot(blk_in_bez)

#gIntersects(UWB[UWB$BEZ == '07',], byid=TRUE)
uwb_in_bez = gBuffer(spTransform(UWB[UWB$BEZ == '07',], ea_projection), byid=T, width=-0.1)
gIsValid(uwb_in_bez)
[1] TRUE
any(xor(gIntersects(uwb_in_bez, byid=T, returnDense = T), diag(1, length(uwb_in_bez)) == 1))
[1] FALSE
plot(uwb_in_bez)

intersection = gIntersection(uwb_in_bez, blk_in_bez, byid = T, drop_lower_td = T)

gIsValid(intersection)
[1] TRUE
plot(intersection)

intersection_uwb_data = intersection %>% over(uwb_in_bez)
intersection_blk_data = intersection %>% over(blk_in_bez)
intersection_area = gArea(intersection, byid=T)
intersection_data = cbind(
    intersection_uwb_data %>% select(UWB_ID=ID, sgbIIu65),
    intersection_blk_data %>% select(BLK, BLK_Einw=Einw),
    data.frame(area=intersection_area) # FIXME how else to normalize?
    )
length(intersection)
[1] 1219
nrow(blk_in_bez)
[1] 1201
# did we miss any blocks?
setdiff(blk_in_bez$BLK, intersection_data$BLK)
character(0)

Pro Block mische die SGB-Werte der Unterblöcke gewichtet nach Fläche. FIXME - besser nach Anzahl der Wohnhäuser?

sgbII_blk = intersection_data %>%
  group_by(BLK) %>%
  summarise(sgbIIu65=sum(sgbIIu65*area)/sum(area)/100)
ggplot(broom::tidy(spTransform(blk_in_bez, wgs84), region='BLK') %>% left_join(sgbII_blk, by=c('id'='BLK'))) + geom_polygon(aes(x=long, y=lat, group=group, fill=sgbIIu65)) + coord_map()

Adjazenz von Blöcken

adjacency = gIntersects(gBuffer(blk_in_bez, byid=T, width=40), byid = T)

adjacency_df = adjacency %>% as.data.frame() %>% mutate(from=row.names(.)) %>%
  gather(to, connected, -from) %>% filter(connected) %>%
  inner_join(spTransform(blk_in_bez, wgs84) %>% coordinates() %>% as.data.frame() %>% rename(from_long=V1, from_lat=V2) %>% mutate(from=row.names(.))) %>%
  inner_join(spTransform(blk_in_bez, wgs84) %>% coordinates() %>% as.data.frame() %>% rename(to_long=V1, to_lat=V2) %>% mutate(to=row.names(.)))
Joining, by = "from"
Joining, by = "to"
ggplot() +
  geom_polygon(aes(x=long, y=lat, group=group), fill='gray', data=broom::tidy(spTransform(blk_in_bez, wgs84), region='BLK')) + 
  geom_segment(aes(x=from_long, y=from_lat, xend=to_long, yend=to_lat), size=0.1, color='black', data=adjacency_df) +
  theme_nothing() + coord_map()

ggsave('figs/adjacency.pdf')
Saving 7 x 5 in image

Select relevant data

optim_kapas = relevant_kapas
optim_kids_in_blks = kids_in_blks %>% filter(kids > 0) %>% inner_join(travel_matrix, by='BLK') %>% select(BLK, kids) %>% mutate(kids=kids)
nrow(optim_kids_in_blks)
[1] 1012
nrow(optim_kapas)
[1] 32
select_schools = as.character(optim_kapas$Schulnummer)
select_blks = as.character(optim_kids_in_blks$BLK)

optim_matrix = inner_join(optim_kids_in_blks, travel_matrix, by='BLK')[select_schools]

dim(optim_matrix)
[1] 1012   32
optim_kapas$Kapa %>% sum
[1] 2584
optim_kids_in_blks$kids %>% sum
[1] 2611.995

Naive Zuordnung: Jeder Block zur nächsten Schule

solution = optim_matrix %>% mutate(BLK=optim_kids_in_blks$BLK) %>% gather(school, dist, -BLK) %>% group_by(BLK) %>% top_n(1, -dist) %>% ungroup

optim_matrix %>% t %>% as.data.frame %>% summarise_each(funs(min)) %>% sum()
[1] 778920.1
solines = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school')) %>% inner_join(cbind(as.data.frame(coordinates(blk[blk$BEZ == bez_id,])), blk[blk$BEZ == bez_id,]@data['BLK']))
Joining, by = "BLK"
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(solution, by=c('id'='BLK'))
ggmap(map, darken = c(0.5, 'white')) + geom_polygon(aes(x=long, y=lat, group=group, fill=school), data=data) +
  geom_segment(aes(x=V1,y=V2,xend=lon,yend=lat), data=solines, size=0.3) +
  geom_point(aes(lon, lat), color='black', size=2, data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
  geom_point(aes(lon, lat, color=spatial_name), data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01)) +
  guides(color=F, fill=F)

Darstellung der Zuordnung als Tabelle

library(formattable)
solution %>% inner_join(optim_kids_in_blks, by='BLK') %>% inner_join(travel_from_blks, by=c('BLK'='BLK', 'school'='dst')) %>%
  group_by(school) %>% summarise(
    kids=sum(kids),
    num_blocks=n(),
    min_dist=min(min),
    avg_dist=mean((kids*avg)/sum(kids)),
    max_dist=max(max)
  ) %>%
  inner_join(relevant_kapas, by=c('school'='Schulnummer')) %>%
  mutate(
    utilization=kids/Kapa
  ) %>% select(
   Schule=school,
   `Blöcke`=num_blocks,
   Kapazität=Kapa,
   Kinder=kids,
   Auslastung=utilization,
   `Weg (min)`=min_dist,
   `Weg (Ø)`=avg_dist,
   `Weg (max)`=max_dist
  ) %>%
  formattable(
    list(
      Kinder = formatter("span", x ~ digits(x, 2)),
      Auslastung = formatter("span",
        style = x ~ style(color = ifelse(x < 1, "green", "red")),
        x ~ icontext(ifelse(x < 1, "ok", "remove"), percent(x))
      ),
      `Weg (Ø)` = proportion_bar("lightblue"),
      `Weg (min)` = proportion_bar("lightblue"),
      `Weg (max)` = proportion_bar("lightblue")
    )
  )
Schule Blöcke Kapazität Kinder Auslastung Weg (min) Weg (Ø) Weg (max)
07G01 14 66 70.90 107.43% 121.760017 677.9233 1235.0122
07G02 18 98 43.64 44.53% 256.574646 647.7419 1086.0563
07G03 17 73 55.24 75.67% 7.781471 416.3732 930.0883
07G05 20 78 101.10 129.61% 95.839722 553.4659 945.7020
07G06 27 55 76.59 139.25% 251.013275 810.1274 1252.2977
07G07 11 81 19.15 23.64% 102.038826 629.6556 1269.2056
07G10 31 112 139.94 124.95% 157.179153 651.1813 1262.8840
07G12 10 75 26.61 35.48% 138.693848 375.7890 680.7033
07G13 27 82 130.83 159.55% 46.293205 475.9641 1113.5214
07G14 34 78 86.69 111.14% 41.618652 424.9613 758.3165
07G15 49 104 209.62 201.56% 161.552948 840.5405 1564.7640
07G16 22 104 58.33 56.09% 1.116924 523.9984 1009.1502
07G17 24 100 68.77 68.77% 47.262344 396.9803 699.3018
07G18 16 44 48.82 110.95% 59.738453 334.8710 686.2927
07G19 31 112 95.64 85.39% 39.771866 1105.6528 2240.9937
07G20 50 81 164.21 202.73% 82.726181 691.1474 1481.9451
07G21 22 72 61.88 85.95% 75.915192 520.5182 1204.4126
07G22 27 91 75.17 82.61% 75.457306 603.4074 1481.8287
07G23 10 50 23.72 47.44% 142.145325 686.4525 1180.7966
07G24 27 75 78.24 104.31% 114.073563 590.3541 1933.3115
07G25 35 75 112.70 150.26% 53.611336 576.0436 1142.3156
07G26 25 52 32.12 61.76% 100.976044 523.4573 1037.3268
07G27 18 75 47.61 63.48% 33.427475 658.6874 1478.6843
07G28 63 75 98.60 131.47% 178.989166 941.6662 2046.0006
07G29 95 104 112.80 108.46% 13.399818 946.3904 2057.0425
07G30 42 72 70.50 97.92% 5.917130 864.2591 2437.8232
07G31 21 78 45.45 58.27% 265.781006 925.6927 1592.6267
07G32 57 78 56.79 72.81% 140.487274 752.3034 1384.1801
07G34 38 100 162.42 162.42% 239.591461 1143.1058 2237.7271
07G35 35 75 92.21 122.94% 43.676998 781.7226 1460.3352
07G36 41 100 62.96 62.96% 169.448837 952.7923 2455.5547
07G37 55 69 82.74 119.92% 102.585381 1268.7165 2216.1509

Daten für die App speichern

Neue Daten

Entities / Schulen

entities = subset(re_schulstand_df, spatial_name %in% relevant_schools) %>%
  rename(entity_id = spatial_name) %>%
  select(-gml_id, -spatial_alias, -spatial_type) %>%
  inner_join(rename(relevant_kapas, capacity=Kapa), by=c('entity_id'='Schulnummer'))
coordinates(entities) = ~ lon + lat
proj4string(entities) = CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
file.remove('app/data/entities.geojson')
[1] TRUE
entities %>% writeOGR('app/data/entities.geojson', layer="entities", driver="GeoJSON", check_exists=F)
entities@data %>% select(entity_id, capacity) %>% write_csv('app/data/entities.csv')

Units / Statistische Blöcke

units = subset(blk, BEZ == bez_id)

units@data$PLR = NULL
units@data$Einw = NULL
units@data$BEZ = NULL
units@data$unit_id = units@data$BLK
units@data$BLK = NULL

units = units %>% sp::merge(
  optim_kids_in_blks %>%
    left_join(sgbII_blk) %>%
    select(unit_id=BLK, population=kids, sgbIIu65)
  )
Joining, by = "BLK"
file.remove('app/data/units.geojson')
[1] TRUE
units %>% writeOGR('app/data/units.geojson', layer="entities", driver="GeoJSON", check_exists=F)
units@data %>% write_csv('app/data/units.csv')

Zuordnung

assignment = solution %>% select(unit_id=BLK, entity_id=school)
assignment %>% write_csv('app/data/assignment.csv')

Weights / Schulwege

travel_from_blks %>%
  rename(unit_id=BLK, entity_id=dst) %>%
  #gather(weight, value, -unit_id, -entity_id) %>%
  write_csv('app/data/weights.csv')

Zusätzliche Daten

file.copy('download/RBS_OD_BEZ_2015_12.geojson', 'app/data/RBS_OD_BEZ_2015_12.geojson')
[1] FALSE
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3IgbGlicywgaW5jbHVkZT1GLCB3YXJuaW5nPUZ9CmxpYnJhcnkocmVhZHIpCmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KHJnZGFsKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dtYXApCmxpYnJhcnkocHVycnIpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkobWFwdG9vbHMpCmxpYnJhcnkocmdlb3MpCmBgYAoKRGllc2VzIE5vdGVib29rIGJlcmVpdGV0IGRpZSBEYXRlbiBmw7xyIGRpZSBJbnRlbGxpZ2VudCBab25pbmcgRW5naW5lIHZvci4gRXMgc3BlaWNoZXJ0CgotIGVudGl0aWVzLmdlb2pzb24g4oCUIFNjaHVsZW4sIGRlcmVuIEdlb2tvb3JkaW5hdGVuIHVuZCBBdHRyaWJ1dGU6IGVudGl0eV9pZCwgY2FwYWNpdHksIHVuZCBhbmRlcmUgQXR0cmlidXRlCi0gZW50aXRpZXMuY3N2IOKAlCBTdGF0aXN0aXNjaGUgQmzDtmNrZSBCZXJsaW5zIHVuZCBvcHRpbWllcnVuZ3NyZWxldmFudGUgQXR0cmlidXRlOiBlbnRpdHlfaWQsIGNhcGFjaXR5Ci0gdW5pdHMuZ2VvanNvbiDigJQgU3RhdGlzdGlzY2hlIEJsw7Zja2UgQmVybGlucywgZGVyZW4gR2VvbWV0cmllIHVuZCBBdHRyaWJ1dGUKLSB1bml0cy5jc3Yg4oCUIFN0YXRzaXRpc2NoZSBCbMO2Y2tlIEJlcmxpbnMgdW5kIG9wdGltaWVydW5nc3JlbGV2YW50ZSBBdHRyaWJ1dGU6IHVuaXRfaWQsIHBvcHVsYXRpb24sIHBlcmNlbnRhZ2Vfc2diCi0gd2VpZ2h0cy5jc3Yg4oCUIG9wdGltaWVydW5nc3JlbGV2YW50ZSBHZXdpY2h0ZSB3aWUgRnXDn3dlZ2UgLyBTcGFsdGVuOiBlbnRpdHlfaWQsIHVuaXRfaWQsIHdlaWdodCwgdmFsdWUKLSBhc3NpZ25tZW50LmNzdiDigJQgZWluZSBpbml0aWFsZSBadW9yZG51bmcgLyBTcGFsdGVuOiB1bml0X2lkLCBlbnRpdHlfaWQKCkJlcmVpdHMgaW4gYW5kZXJlbiBzY3JpcHRlbiB3dXJkZSB2b3JiZXJlaXRldDoKCi0gRnXDn3dlZ2VuIHZvbiBlaW5lciBncm/Dn2VuIFNpY2hwcm9iZSB2b24gKF9Xb2huXy0pR2Viw6R1ZGVuIHp1IGFsbGVuIFNjaHVsZW4gd3VyZGVuIGJlcmVjaG5ldCB1bmQgaW4gYHJvdXRlX21hdHJpeC5jc3ZgIGdlc3BlaWNoZXJ0Ci0gRGllIFN0aWNocHJvYmUgd3VyZGUgaW4gYHNhbXBsZWRfYnVpbGRpbmdzLmNzdmAgZ2VzcGVpY2hlcnQKCkRpZSBEYXRlbiB3ZXJkZW4gd2llZm9sZ3Qgdm9yYmVyZWl0ZXQ6CgotIHBybyBCbG9jayB3ZXJkZW4gZGllIEFuemFobCBkZXIgZWluenVzY2h1bGVuZGVuIEtpbmRlciBtaXQgSGlsZmUgZGVyIEVpbndvaG5lcnphaGxlbiBuYWNoIEFsdGVyIGF1ZiBMT1ItRWJlbmUgaW4gYEVXUjIwMTUxMkVfTWF0cml4LmNzdmAgaG9jaGdlcmVjaG5ldAogICAgLSBLaW5kZXIgZGVzIExPUiB3ZXJkZW4gQW50ZWlsaWcgbmFjaCBFaW53b2huZXJ6YWhsIGRlcyBCbG9ja3MgaW0gVmVyaMOkbHRuaXMgenVtIExPUiBhdWYgZGllIEJsw7Zja2UgdmVydGVpbHQKLSBlcyB3ZXJkZW4gbWluaW1hbGUsIGR1cmNoc2Nobml0dGxpY2hlIHVuZCBtYXhpbWFsZSBGdcOfd2VnZSBhdXMgamVkZW0gQmxvY2sgZXJyZWNobmV0CgpUT0RPOgotIGRpZSBzb3ppb8O2a29ub21pc2NoZW4gRmFrdG9yZW4gd2VyZGVuIGF1cyBkZW4gV2FobGJlemlya2VuIGF1ZiBkaWUgQmzDtmNrZSBob2NoZ2VyZWNobmV0IChodHRwczovL2dpdGh1Yi5jb20vYmVybGluZXJtb3JnZW5wb3N0L2NvZ3JhbikKCiMjIExhZGVuIGRlciBEYXRlbgoKYGBge3IgbG9hZCBkYXRhLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpzYW1wbGVkX2J1aWxkaW5ncyA9IHJlYWRfcmRzKCdvdXRwdXQvc2FtcGxlZF9idWlsZGluZ3MucmRzJykKYmV6ID0gcmVhZE9HUignZG93bmxvYWQvUkJTX09EX0JFWl8yMDE1XzEyLmdlb2pzb24nLCBsYXllciA9ICdPR1JHZW9KU09OJywgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpibGsgPSByZWFkT0dSKCdkb3dubG9hZC9SQlNfT0RfQkxLXzIwMTVfMTIuZ2VvanNvbicsIGxheWVyID0gJ09HUkdlb0pTT04nLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmxvciA9IHJlYWRPR1IoJ2Rvd25sb2FkL1JCU19PRF9MT1JfMjAxNV8xMi5nZW9qc29uJywgbGF5ZXIgPSAnT0dSR2VvSlNPTicsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKcmVfc2NodWxzdGFuZCA9IHJlYWRPR1IoJ2Rvd25sb2FkL3JlX3NjaHVsc3RhbmQuZ2VvanNvbicsIGxheWVyID0gJ09HUkdlb0pTT04nLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmBgYAoKIyMjIFNjaHVsd2VnZQoKYGBge3J9CnJvdXRlX21hdHJpeCA9IHJlYWRfcmRzKCdvdXRwdXQvcm91dGVfbWF0cml4LnJkcycpCmBgYAoKIyMjIFNjaHVsa2FwYXppdMOkdGVuIHVuZCBFaW53b2huZXJ6YWhsZXIgYXVmIExPUi1FYmVuZQoKYGBge3J9CmthcGFzID0gcmVhZF9jc3YoJ2Rvd25sb2FkL2FubWVsZGV6YWhsZW4uY3N2JykgJT4lIGZpbHRlcihncmVwbCgnRycsIFNjaHVsbnVtbWVyKSkgJT4lIGZpbHRlcighaXMubmEoYFBsw6R0emVgKSkKZWlud29obmVyX2xvciA9IHJlYWRfZGVsaW0oJ2Rvd25sb2FkL0VXUjIwMTUxMkVfTWF0cml4LmNzdicsIGRlbGltPSc7JykKYGBgCgojIyDDnGJlcnByw7xmdW5nIGRlciBWb2xsc3TDpG5kaWdrZWl0IGRlciBEYXRlbiDDvGJlciBBbm1lbGRlemFobGVuL0thcGF6aXTDpHRlbgoKYGBge3J9CnJlX3NjaHVsc3RhbmRfZGYgPSByZV9zY2h1bHN0YW5kICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHJlbmFtZShsb249Y29vcmRzLngxLCBsYXQ9Y29vcmRzLngyKQpyZV9zY2h1bHN0YW5kX2RmICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgbXV0YXRlKEJFWklSSz1lbmMydXRmOChhcy5jaGFyYWN0ZXIoQkVaSVJLKSkpICU+JQogIGdyb3VwX2J5KEJFWklSSykgJT4lIHN1bW1hcmlzZShgQW56YWhsIFNjaHVsZW5gID0gbigpKSAlPiUKICByZW5hbWUoQmV6aXJrPUJFWklSSykgJT4lIGxlZnRfam9pbihrYXBhcyAlPiUgZ3JvdXBfYnkoQmV6aXJrKSAlPiUgc3VtbWFyaXNlKGBNaXQgS2FwYXppdMOkdGAgPSBuKCkpKQpgYGAKCkbDvHIgd2VsY2hlIEJlemlya2UgaGFiZW4gd2lyIGbDvHIgYWxsZSBTY2h1bGVuIEthcGF6aXTDpHRlbiBnZWdlYmVuPwoKYGBge3J9CnJlX3NjaHVsc3RhbmQgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZmlsdGVyKGdyZXBsKCdHJywgc3BhdGlhbF9uYW1lKSkgJT4lIG11dGF0ZShCRVpJUks9ZW5jMnV0ZjgoYXMuY2hhcmFjdGVyKEJFWklSSykpKSAlPiUgZ3JvdXBfYnkoQkVaSVJLKSAlPiUgc3VtbWFyaXNlKGBBbnphaGwgU2NodWxlbmAgPSBuKCkpICU+JQogIHJlbmFtZShCZXppcms9QkVaSVJLKSAlPiUgbGVmdF9qb2luKGthcGFzICU+JSBncm91cF9ieShCZXppcmspICU+JSBzdW1tYXJpc2UoYE1pdCBLYXBheml0w6R0YCA9IG4oKSkpICU+JSBmaWx0ZXIoYEFuemFobCBTY2h1bGVuYCA9PSBgTWl0IEthcGF6aXTDpHRgKQpgYGAKCsOcYmVycHLDvGZ1bmcgb2IgZGllIExpc3RlIGRlciBTY2h1bGVuIHVuZCBMaXN0ZSBkZXIgU2NodWxlbiBtaXQgS2FwYXppdMOkdHNpbmZvcm1hdGlvbmVuIGdsZWljaCBzaW5kOgoKYGBge3J9CmJlemlyayA9ICdUZW1wZWxob2YtU2Now7ZuZWJlcmcnCnNjaHVsZW5fbWl0X2thcGEgPSBrYXBhcyAlPiUgZmlsdGVyKEJlemlyayA9PSBiZXppcmspICU+JSAuJFNjaHVsbnVtbWVyCnNjaHVsZW5fbWl0X2thcGEKZ3J1bmRzY2h1bGVuID0gcmVfc2NodWxzdGFuZCAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgZmlsdGVyKEJFWklSSyA9PSBiZXppcmspICU+JSAuJHNwYXRpYWxfbmFtZQonSW4gQW5tZWxkZWxpc3RlLCBmZWhsdCBpbiBTY2h1bHN0YW5kJwpzZXRkaWZmKHNjaHVsZW5fbWl0X2thcGEsIGdydW5kc2NodWxlbikKJ0luIHJlX3NjaHVsc3RhbmQsIGZlaGx0IGluIEFubWVsZGVsaXN0ZScKc2V0ZGlmZihncnVuZHNjaHVsZW4sIHNjaHVsZW5fbWl0X2thcGEpCmBgYAoKCmBgYHtyfQptYXAgPSBnZXRfbWFwKCdCZXJsaW4nKQpgYGAKCgpgYGB7cn0KcmVfc2NodWxzdGFuZF9kZl93X2thcGFzID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgbGVmdF9qb2luKGthcGFzLCBieT1jKCdzcGF0aWFsX25hbWUnPSdTY2h1bG51bW1lcicpKQpgYGAKClBsb3QgYWxsZXIgU2NodWxlbiwgbWl0IGRlciBJbmZvLCBvYiBLYXBheml0w6R0c2luZm9ybWF0aW9uZW4gdmVyZsO8Z2JhciBzaW5kLgoKYGBge3J9CmRhdGEgPSByZV9zY2h1bHN0YW5kX2RmX3dfa2FwYXMgJT4lIGZpbHRlcihncmVwbCgnRycsIHNwYXRpYWxfbmFtZSkpICU+JSBmaWx0ZXIoQkVaSVJLPT1iZXppcmspICU+JSBtdXRhdGUobWlzc2luZy5jYXBhPWlzLm5hKGBQbMOkdHplYCkpCmdnbWFwKG1hcCkgKyBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCwgY29sb3I9bWlzc2luZy5jYXBhKSwgZGF0YT1kYXRhKSArCiAgICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbiktMC4wMSwgbWF4KGRhdGEkbG9uKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgpGaWx0ZXIgYXVmIFNjaHVsZW4gbWl0IEthcGF6aXTDpHRzaW5mb3JtYXRpb25lbiAoZsO8ciBULVMgc2luZCBkYXMgYWxsZSk6CgpgYGB7cn0KcmVsZXZhbnRfc2Nob29scyA9IHJlX3NjaHVsc3RhbmRfZGZfd19rYXBhcyAlPiUgZmlsdGVyKGdyZXBsKCdHJywgc3BhdGlhbF9uYW1lKSkgJT4lIGZpbHRlcihCRVpJUks9PWJlemlyayAmICFpcy5uYShgUGzDpHR6ZWApKSAlPiUgLiRzcGF0aWFsX25hbWUKcmVsZXZhbnRfc2Nob29scwpgYGAKCiMjIE1hcHBpbmcgQmV6aXJrLT5MT1ItPkJsb2NrCgpgYGB7cn0KZGZfYmV6ID0gYXMuZGF0YS5mcmFtZShiZXopCmRmX2xvciA9IGFzLmRhdGEuZnJhbWUobG9yKQpkZl9ibGsgPSBhcy5kYXRhLmZyYW1lKGJsaykKYGBgCgojIyMgU2FuaXR5LUNoZWNrOiBMT1JzIHVuZCBCbMO2Y2tlIGltIEJlemlyawoKYGBge3J9CmJlel9pZCA9IGZpbHRlcihkZl9iZXosIEJFWk5BTUUgPT0gYmV6aXJrKSRCRVoKcmVsZXZhbnRfbG9ycyA9IGRmX2xvciAlPiUgZmlsdGVyKEJFWiA9PSBiZXpfaWQpCnJlbGV2YW50X2Jsa3MgPSBkZl9ibGsgJT4lIGZpbHRlcihCRVogPT0gYmV6X2lkKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWxvcltsb3IkQkVaID09IGJlel9pZCxdKSArIGNvb3JkX21hcCgpICsgZ2VvbV9wYXRoKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGRhdGE9YmV6LCBjb2xvcj0ncmVkJykKYGBgCgojIyMgQmzDtmNrZSBpbSBCZXppcmsKCmBgYHtyfQpnZ3Bsb3QoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWJsa1tibGskQkVaID09IGJlel9pZCxdKSArIGNvb3JkX21hcCgpICsgZ2VvbV9wYXRoKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGRhdGE9YmV6W2JleiRCRVogPT0gYmV6X2lkLF0sIGNvbG9yPSdyZWQnKQpgYGAKCiMjIEtpbmRlciBpbSBCZXppcmsgYXVmIEJsw7Zja2UgaG9jaHJlY2huZW4KCsOcYmVyIGRpZSBFaW53b2huZXJpbmZvcm1hdGlvbmVuIGluIGBSQlNfT0RfQkxLXzIwMTVfMTIuZ2VvanNvbmAga2FubiBgRVdSMjAxNTEyRV9NYXRyaXguY3N2YCB2b24gTE9SLUViZW5lIGF1ZiBCbG9ja2ViZW5lIGhvY2hnZXJlY2huZXQgd2VyZGVuLgoKVE9ETzogU3RhdHRkZXNzZW4gbWl0IGh0dHBzOi8vZ2l0aHViLmNvbS9iZXJsaW5lcm1vcmdlbnBvc3QvY29ncmFuIG1hY2hlbj8KCiMjIyBQbG90IGRlciA2LUrDpGhyaWdlbiBuYWNoIGBFV1IyMDE1MTJFX01hdHJpeC5jc3ZgCgpXaXIgdmVyd2VuZGVuIGRhcyBtaXR0ZWwgZGVyIDUtIHVuZCA2LUrDpGhyaWdlbi4KClRPRE8gbmV1ZSBEYXRlbiB2b24gVG9ycmVzPwpUT0RPIFByb2dub3NlPwoKYGBge3J9CnJlbGV2YW50X2V3ciA9IGVpbndvaG5lcl9sb3IgJT4lCiAgc2VsZWN0KFJBVU1JRCwgRV9FMDVfMDYsIEVfRTA2XzA3KSAlPiUKICBmaWx0ZXIoUkFVTUlEICVpbiUgcmVsZXZhbnRfbG9ycyRQTFIpICU+JQogICMgU2Nobml0dCBkZXIgNSB1bmQgNi1Kw6RocmlnZW4KICBtdXRhdGUoa2lkcz0oYXMubnVtZXJpYyhnc3ViKCcsJywnLicsRV9FMDZfMDcpKSthcy5udW1lcmljKGdzdWIoJywnLCcuJyxFX0UwNV8wNikpKS8yKSAlPiUgYXMuZGF0YS5mcmFtZSgpCgpkYXRhID0gdGlkeShsb3JbbG9yJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdQTFInKSAlPiUgaW5uZXJfam9pbihyZWxldmFudF9ld3IsIGJ5PWMoJ2lkJz0nUkFVTUlEJykpCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPWtpZHMpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCiMjIyBQbG90IGRlciBFaW53b2huZXIgYXVmIEJsb2NrZWJlbmUKCmBgYHtyfQpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgaW5uZXJfam9pbihkZl9ibGssIGJ5PWMoJ2lkJz0nQkxLJykpICU+JSBtdXRhdGUoRWludz1pZmVsc2UoRWludz09MCwgTkEsIEVpbncpKQowCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPUVpbncpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCgojIyMgSG9jaHJyZWNobnVuZyBhdWYgQmzDtmNrZSwgU3RydWt0dXJxdW90ZQoKYGBge3J9ClN0cnVrdHVycXVvdGUgPSAwLjkKCmtpZHNfaW5fYmxrcyA9IHJlbGV2YW50X2Jsa3MgJT4lCiAgZ3JvdXBfYnkoUExSKSAlPiUKICBtdXRhdGUoRWlud1JhdGlvID0gRWludy9zdW0oRWludykpICU+JQogIHVuZ3JvdXAgJT4lCiAgbGVmdF9qb2luKHJlbGV2YW50X2V3ciwgYnk9YygnUExSJz0nUkFVTUlEJykpICU+JQogIG11dGF0ZShraWRzID0gRWlud1JhdGlvKmtpZHMpICU+JQogIG11dGF0ZShraWRzID0gU3RydWt0dXJxdW90ZSpraWRzKSAlPiUgIyBTdHJ1a3R1cnF1b3RlCiAgc2VsZWN0KEJFWiwgUExSLCBCTEssIEVpbncsIGtpZHMpICU+JQogIGFzLmRhdGEuZnJhbWUoKQpyb3cubmFtZXMoa2lkc19pbl9ibGtzKSA9IGtpZHNfaW5fYmxrcyRCTEsKCmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBpbm5lcl9qb2luKGtpZHNfaW5fYmxrcywgYnk9YygnaWQnPSdCTEsnKSkgJT4lIG11dGF0ZShraWRzPWlmZWxzZShraWRzPT0wLCBOQSwga2lkcyksIEVpbnc9aWZlbHNlKEVpbnc9PTAsIE5BLCBFaW53KSkKCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPWtpZHMpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCiMjIFZlcmbDvGdiYXJlIFBsw6R0emUKCmBgYHtyfQpyZWxldmFudF9rYXBhcyA9IGthcGFzICU+JSBzZWxlY3QoU2NodWxudW1tZXIsIEthcGE9YFBsw6R0emVgKSAlPiUgZmlsdGVyKFNjaHVsbnVtbWVyICVpbiUgcmVsZXZhbnRfc2Nob29scykgJT4lIGFzLmRhdGEuZnJhbWUoKQojcm93Lm5hbWVzKHJlbGV2YW50X2thcGFzKSA9IHJlbGV2YW50X2thcGFzJFNjaHVsbnVtbWVyCmBgYAoKIyMjIMOcYmVycHLDvGZ1bmcgZGVyIFN1bW1lIGRlciBLYXBheml0w6R0ZW4sIEFubWVsZHVuZ2VuIHVuZCBLaW5kZXJzdGF0aXN0aWtlbgoKYGBge3J9CidTdW1tZSBLYXBhcycKcmVsZXZhbnRfa2FwYXMgJT4lIC4kS2FwYSAlPiUgc3VtCidBbm1lbGR1bmdlbicKa2FwYXMgJT4lIG11dGF0ZShBbm1lbGR1bmdlbiA9IGFzLm51bWVyaWMoZ3N1YignW14wLTldJywgJycsIEFubWVsZHVuZ2VuKSkpICU+JSBmaWx0ZXIoU2NodWxudW1tZXIgJWluJSByZWxldmFudF9zY2hvb2xzKSAlPiUgLiRBbm1lbGR1bmdlbiAlPiUgc3VtCidLaWRzIGxhdXQgU3RhdGlzdGlrJwpraWRzX2luX2Jsa3Mka2lkcyAlPiUgc3VtCnJlbGV2YW50X2V3ciRraWRzICU+JSBzdW0KYGBgCgojIyBTY2h1bHdlZ2Ugdm9uIEJsw7Zja2VuIHp1IFNjaHVsZW4gYWdncmVnaWVyZW4KCkbDvHIgamVkZXMgV29obmdlYsOkdWRlIHN1Y2hlbiB3aXIgZGVuIHp1Z2Vow7ZyaWdlbiBCbG9jawoKYGBge3J9CnJlc2lkZW50aWFsX2J1aWxkaW5nc19ibG9ja3MgPSBzYW1wbGVkX2J1aWxkaW5ncyAlPiUgaW5uZXJfam9pbihkZl9ibGspICU+JSBmaWx0ZXIoQkVaID09IGJlel9pZCkKcmVzaWRlbnRpYWxfYnVpbGRpbmdzX2Jsb2NrcwpgYGAKCmBgYHtyfQpyb3V0ZXNfZnJvbV9ibGtzID0gcmVzaWRlbnRpYWxfYnVpbGRpbmdzX2Jsb2NrcyAlPiUKICBsZWZ0X2pvaW4ocm91dGVfbWF0cml4ICU+JSBmaWx0ZXIoZHN0ICVpbiUgcmVsZXZhbnRfc2Nob29scyksIGJ5PWMoJ09JJz0nc3JjJykpCmhlYWQocm91dGVzX2Zyb21fYmxrcykKYGBgCgojIyMgUGxvdCBkZXIgcmVsZXZhbnRlbiBCbMO2Y2tlIChtaXQgV29obmdlYsOkdWRlbikKCmBgYHtyfQpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgaW5uZXJfam9pbihyb3V0ZXNfZnJvbV9ibGtzICU+JSBncm91cF9ieShCTEspICU+JSBzdW1tYXJpc2Uobj1uKCkpLCBieT1jKCdpZCc9J0JMSycpKQpnZ21hcChtYXApICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGZpbGw9J3JlZCcsIGRhdGE9ZGF0YSkgKwogICNnZW9tX3BvaW50KGFlcyh4PWxvbiwgeT1sYXQpLCBkYXRhPXJiX2RmLCBjb2xvcj0nYmxhY2snLCBzaXplPTAuMDEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCgpgYGB7cn0KdHJhdmVsX2Zyb21fYmxrcyA9IHJvdXRlc19mcm9tX2Jsa3MgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZ3JvdXBfYnkoQkxLLCBkc3QpICU+JSBzdW1tYXJpc2UobWluPW1pbihkaXN0YW5jZSksIGF2Zz1tZWFuKGRpc3RhbmNlKSwgbWVkPW1lZGlhbihkaXN0YW5jZSksIG1heD1tYXgoZGlzdGFuY2UpKSAlPiUgdW5ncm91cAp0cmF2ZWxfZnJvbV9ibGtzCmBgYAoKIyMjIFBsb3QgZGVyIEJsw7Zja2UgbWl0IEbDpHJidW5nIG5hY2ggZHVyY2hzY2huaXR0bGljaGVtIFdlZyB6dXIgbsOkY2hzdGVuIFNjaHVsZQoKYGBge3J9CmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBsZWZ0X2pvaW4odHJhdmVsX2Zyb21fYmxrcyAlPiUgZ3JvdXBfYnkoQkxLKSAlPiUgdG9wX24oMSwgLWF2ZyksIGJ5PWMoJ2lkJz0nQkxLJykpCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPS1hdmcpLCBkYXRhPWRhdGEpICsKICBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCksIGNvbG9yPSdyZWQnLCBkYXRhID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgZmlsdGVyKEJFWklSSz09YmV6aXJrICYgU0NIVUxBUlQ9PSdHcnVuZHNjaHVsZScpKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgoKYGBge3J9CnRyYXZlbF9tYXRyaXggPSB0cmF2ZWxfZnJvbV9ibGtzICU+JSBzZWxlY3QoQkxLLCBkc3QsIGF2ZykgJT4lIHNwcmVhZChkc3QsIGF2ZykKZGltKHRyYXZlbF9tYXRyaXgpCnRyYXZlbF9tYXRyaXgKYGBgCgojIyBTb3ppb8O2a29ub21pc2NoZSBEYXRlbgoKV2lyIGhhYmVuIFNvemlvw7Zrb25vbWlzY2hlIERhdGVuIGluIGRlbiBXYWhsYmV6aXJrZW4uIFN0cmF0ZWdpZToKLSBTY2huZWlkZW4gZGVyIFdhaGxiZXppcmtlIG1pdCBkZW4gQmzDtmNrZW4KLSDDnGJlcm5haG1lIGRlcyBQcm96ZW50d2VydGVzIHZvbSBXYWhsYmV6aXJrIGbDvHIgamVkZW4gKFVudGVyLSlibG9jawotIFp1b3JkbnVuZyB6dSBqZWRlbSBCbG9jayB1bmQgVmVyZWluaWd1bmcgZHVyY2ggRmzDpGNoZW4vV29obmhhdXMtZ2V3aWNodGV0ZXMgTWl0dGVsIGRlcyBQcm96ZW50d2VydGVzIAoKYGBge3J9ClVXQiA9IHJlYWRPR1IoJ2Rvd25sb2FkL1JCU19PRF9VV0JfQUdIMjAxNi5nZW9qc29uJywgbGF5ZXIgPSAnT0dSR2VvSlNPTicsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKVVdCJElEID0gcGFzdGUwKFVXQiRCRVosIFVXQiRVV0IpCnNvemlvX1VXQiA9IHJlYWRfZXhjZWwoJ2Rvd25sb2FkL0RMX0JFX0FIMjAxNl9TdHJ1a3R1cmRhdGVuLnhsc3gnLCBzaGVldCA9IDMpICU+JSBzZWxlY3QoSUQsIHNnYklJdTY1PWBFaW53b2huZXIgdW50ZXIgNjUgaW4gU0dCIElJIDIwMTQgUHJvemVudGApClVXQiA9IFVXQiAlPiUgc3A6Om1lcmdlKHNvemlvX1VXQiwgYnk9J0lEJykKCmdncGxvdChicm9vbTo6dGlkeShVV0IsIHJlZ2lvbj0nSUQnKSAlPiUgaW5uZXJfam9pbihVV0IgJT4lIGFzLmRhdGEuZnJhbWUsIGJ5PWMoJ2lkJz0nSUQnKSkpICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1zZ2JJSXU2NSkpICsgY29vcmRfbWFwKCkKYGBgCgpDaGVjayBmb3Igc2VsZiBpbnRlcnNlY3Rpb25zCmBgYHtyfQp3Z3M4NCA9IENSUyhwcm9qNHN0cmluZyhibGspKQplYV9wcm9qZWN0aW9uID0gQ1JTKCIrcHJvaj1sYWVhICtsYXRfMD01MiArbG9uXzA9MTAgK3hfMD00MzIxMDAwICt5XzA9MzIxMDAwMCArZWxscHM9R1JTODAgK3Rvd2dzODQ9MCwwLDAsMCwwLDAsMCArdW5pdHM9bSArbm9fZGVmcyIpCmJsa19pbl9iZXogPSBibGtbYmxrJEJFWiA9PSAnMDcnLF0KZ0lzVmFsaWQoYmxrX2luX2JleikKYmxrX2luX2JleiA9IGdCdWZmZXIoc3BUcmFuc2Zvcm0oYmxrX2luX2JleiwgZWFfcHJvamVjdGlvbiksIGJ5aWQ9VCwgd2lkdGggPSAtMC4xKQphbnkoeG9yKGdJbnRlcnNlY3RzKGJsa19pbl9iZXosIGJ5aWQ9VCwgcmV0dXJuRGVuc2UgPSBUKSwgZGlhZygxLCBsZW5ndGgoYmxrX2luX2JleikpID09IDEpKQpwbG90KGJsa19pbl9iZXopCiNnSW50ZXJzZWN0cyhVV0JbVVdCJEJFWiA9PSAnMDcnLF0sIGJ5aWQ9VFJVRSkKYGBgCgpgYGB7cn0KdXdiX2luX2JleiA9IGdCdWZmZXIoc3BUcmFuc2Zvcm0oVVdCW1VXQiRCRVogPT0gJzA3JyxdLCBlYV9wcm9qZWN0aW9uKSwgYnlpZD1ULCB3aWR0aD0tMC4xKQpnSXNWYWxpZCh1d2JfaW5fYmV6KQphbnkoeG9yKGdJbnRlcnNlY3RzKHV3Yl9pbl9iZXosIGJ5aWQ9VCwgcmV0dXJuRGVuc2UgPSBUKSwgZGlhZygxLCBsZW5ndGgodXdiX2luX2JleikpID09IDEpKQpwbG90KHV3Yl9pbl9iZXopCmBgYAoKYGBge3J9CmludGVyc2VjdGlvbiA9IGdJbnRlcnNlY3Rpb24odXdiX2luX2JleiwgYmxrX2luX2JleiwgYnlpZCA9IFQsIGRyb3BfbG93ZXJfdGQgPSBUKQoKZ0lzVmFsaWQoaW50ZXJzZWN0aW9uKQpwbG90KGludGVyc2VjdGlvbikKYGBgCgpgYGB7cn0KaW50ZXJzZWN0aW9uX3V3Yl9kYXRhID0gaW50ZXJzZWN0aW9uICU+JSBvdmVyKHV3Yl9pbl9iZXopCmludGVyc2VjdGlvbl9ibGtfZGF0YSA9IGludGVyc2VjdGlvbiAlPiUgb3ZlcihibGtfaW5fYmV6KQppbnRlcnNlY3Rpb25fYXJlYSA9IGdBcmVhKGludGVyc2VjdGlvbiwgYnlpZD1UKQppbnRlcnNlY3Rpb25fZGF0YSA9IGNiaW5kKAogICAgaW50ZXJzZWN0aW9uX3V3Yl9kYXRhICU+JSBzZWxlY3QoVVdCX0lEPUlELCBzZ2JJSXU2NSksCiAgICBpbnRlcnNlY3Rpb25fYmxrX2RhdGEgJT4lIHNlbGVjdChCTEssIEJMS19FaW53PUVpbncpLAogICAgZGF0YS5mcmFtZShhcmVhPWludGVyc2VjdGlvbl9hcmVhKSAjIEZJWE1FIGhvdyBlbHNlIHRvIG5vcm1hbGl6ZT8KICAgICkKYGBgCgpgYGB7cn0KbGVuZ3RoKGludGVyc2VjdGlvbikKbnJvdyhibGtfaW5fYmV6KQojIGRpZCB3ZSBtaXNzIGFueSBibG9ja3M/CnNldGRpZmYoYmxrX2luX2JleiRCTEssIGludGVyc2VjdGlvbl9kYXRhJEJMSykKYGBgCgpQcm8gQmxvY2sgbWlzY2hlIGRpZSBTR0ItV2VydGUgZGVyIFVudGVyYmzDtmNrZSBnZXdpY2h0ZXQgbmFjaCBGbMOkY2hlLgpGSVhNRSAtIGJlc3NlciBuYWNoIEFuemFobCBkZXIgV29obmjDpHVzZXI/CmBgYHtyfQpzZ2JJSV9ibGsgPSBpbnRlcnNlY3Rpb25fZGF0YSAlPiUKICBncm91cF9ieShCTEspICU+JQogIHN1bW1hcmlzZShzZ2JJSXU2NT1zdW0oc2diSUl1NjUqYXJlYSkvc3VtKGFyZWEpLzEwMCkKYGBgCgoKYGBge3J9CmdncGxvdChicm9vbTo6dGlkeShzcFRyYW5zZm9ybShibGtfaW5fYmV6LCB3Z3M4NCksIHJlZ2lvbj0nQkxLJykgJT4lIGxlZnRfam9pbihzZ2JJSV9ibGssIGJ5PWMoJ2lkJz0nQkxLJykpKSArIGdlb21fcG9seWdvbihhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXAsIGZpbGw9c2diSUl1NjUpKSArIGNvb3JkX21hcCgpCmBgYAoKIyMgQWRqYXplbnogdm9uIEJsw7Zja2VuCgpgYGB7cn0KYWRqYWNlbmN5ID0gZ0ludGVyc2VjdHMoZ0J1ZmZlcihibGtfaW5fYmV6LCBieWlkPVQsIHdpZHRoPTQwKSwgYnlpZCA9IFQpCgphZGphY2VuY3lfZGYgPSBhZGphY2VuY3kgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgbXV0YXRlKGZyb209cm93Lm5hbWVzKC4pKSAlPiUKICBnYXRoZXIodG8sIGNvbm5lY3RlZCwgLWZyb20pICU+JSBmaWx0ZXIoY29ubmVjdGVkKSAlPiUKICBpbm5lcl9qb2luKHNwVHJhbnNmb3JtKGJsa19pbl9iZXosIHdnczg0KSAlPiUgY29vcmRpbmF0ZXMoKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSByZW5hbWUoZnJvbV9sb25nPVYxLCBmcm9tX2xhdD1WMikgJT4lIG11dGF0ZShmcm9tPXJvdy5uYW1lcyguKSkpICU+JQogIGlubmVyX2pvaW4oc3BUcmFuc2Zvcm0oYmxrX2luX2Jleiwgd2dzODQpICU+JSBjb29yZGluYXRlcygpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHJlbmFtZSh0b19sb25nPVYxLCB0b19sYXQ9VjIpICU+JSBtdXRhdGUodG89cm93Lm5hbWVzKC4pKSkKCmdncGxvdCgpICsKICBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwKSwgZmlsbD0nZ3JheScsIGRhdGE9YnJvb206OnRpZHkoc3BUcmFuc2Zvcm0oYmxrX2luX2Jleiwgd2dzODQpLCByZWdpb249J0JMSycpKSArIAogIGdlb21fc2VnbWVudChhZXMoeD1mcm9tX2xvbmcsIHk9ZnJvbV9sYXQsIHhlbmQ9dG9fbG9uZywgeWVuZD10b19sYXQpLCBzaXplPTAuMSwgY29sb3I9J2JsYWNrJywgZGF0YT1hZGphY2VuY3lfZGYpICsKICB0aGVtZV9ub3RoaW5nKCkgKyBjb29yZF9tYXAoKQoKZ2dzYXZlKCdmaWdzL2FkamFjZW5jeS5wZGYnKQpgYGAKCgojIyBTZWxlY3QgcmVsZXZhbnQgZGF0YQoKYGBge3J9Cm9wdGltX2thcGFzID0gcmVsZXZhbnRfa2FwYXMKb3B0aW1fa2lkc19pbl9ibGtzID0ga2lkc19pbl9ibGtzICU+JSBmaWx0ZXIoa2lkcyA+IDApICU+JSBpbm5lcl9qb2luKHRyYXZlbF9tYXRyaXgsIGJ5PSdCTEsnKSAlPiUgc2VsZWN0KEJMSywga2lkcykgJT4lIG11dGF0ZShraWRzPWtpZHMpCm5yb3cob3B0aW1fa2lkc19pbl9ibGtzKQpucm93KG9wdGltX2thcGFzKQoKc2VsZWN0X3NjaG9vbHMgPSBhcy5jaGFyYWN0ZXIob3B0aW1fa2FwYXMkU2NodWxudW1tZXIpCnNlbGVjdF9ibGtzID0gYXMuY2hhcmFjdGVyKG9wdGltX2tpZHNfaW5fYmxrcyRCTEspCgpvcHRpbV9tYXRyaXggPSBpbm5lcl9qb2luKG9wdGltX2tpZHNfaW5fYmxrcywgdHJhdmVsX21hdHJpeCwgYnk9J0JMSycpW3NlbGVjdF9zY2hvb2xzXQoKZGltKG9wdGltX21hdHJpeCkKCm9wdGltX2thcGFzJEthcGEgJT4lIHN1bQpvcHRpbV9raWRzX2luX2Jsa3Mka2lkcyAlPiUgc3VtCmBgYAoKIyMgTmFpdmUgWnVvcmRudW5nOiBKZWRlciBCbG9jayB6dXIgbsOkY2hzdGVuIFNjaHVsZQoKYGBge3J9CnNvbHV0aW9uID0gb3B0aW1fbWF0cml4ICU+JSBtdXRhdGUoQkxLPW9wdGltX2tpZHNfaW5fYmxrcyRCTEspICU+JSBnYXRoZXIoc2Nob29sLCBkaXN0LCAtQkxLKSAlPiUgZ3JvdXBfYnkoQkxLKSAlPiUgdG9wX24oMSwgLWRpc3QpICU+JSB1bmdyb3VwCgpvcHRpbV9tYXRyaXggJT4lIHQgJT4lIGFzLmRhdGEuZnJhbWUgJT4lIHN1bW1hcmlzZV9lYWNoKGZ1bnMobWluKSkgJT4lIHN1bSgpCgpzb2xpbmVzID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgaW5uZXJfam9pbihzb2x1dGlvbiwgYnk9Yygnc3BhdGlhbF9uYW1lJz0nc2Nob29sJykpICU+JSBpbm5lcl9qb2luKGNiaW5kKGFzLmRhdGEuZnJhbWUoY29vcmRpbmF0ZXMoYmxrW2JsayRCRVogPT0gYmV6X2lkLF0pKSwgYmxrW2JsayRCRVogPT0gYmV6X2lkLF1AZGF0YVsnQkxLJ10pKQoKZGF0YSA9IHRpZHkoYmxrW2JsayRCRVogPT0gYmV6X2lkLF0sIHJlZ2lvbj0nQkxLJykgJT4lIGxlZnRfam9pbihzb2x1dGlvbiwgYnk9YygnaWQnPSdCTEsnKSkKZ2dtYXAobWFwLCBkYXJrZW4gPSBjKDAuNSwgJ3doaXRlJykpICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1zY2hvb2wpLCBkYXRhPWRhdGEpICsKICBnZW9tX3NlZ21lbnQoYWVzKHg9VjEseT1WMix4ZW5kPWxvbix5ZW5kPWxhdCksIGRhdGE9c29saW5lcywgc2l6ZT0wLjMpICsKICBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCksIGNvbG9yPSdibGFjaycsIHNpemU9MiwgZGF0YSA9IHJlX3NjaHVsc3RhbmRfZGYgJT4lIGlubmVyX2pvaW4oc29sdXRpb24sIGJ5PWMoJ3NwYXRpYWxfbmFtZSc9J3NjaG9vbCcpKSkgKwogIGdlb21fcG9pbnQoYWVzKGxvbiwgbGF0LCBjb2xvcj1zcGF0aWFsX25hbWUpLCBkYXRhID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgaW5uZXJfam9pbihzb2x1dGlvbiwgYnk9Yygnc3BhdGlhbF9uYW1lJz0nc2Nob29sJykpKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkgKwogIGd1aWRlcyhjb2xvcj1GLCBmaWxsPUYpCmBgYAoKIyMgRGFyc3RlbGx1bmcgZGVyIFp1b3JkbnVuZyBhbHMgVGFiZWxsZQoKYGBge3J9CmxpYnJhcnkoZm9ybWF0dGFibGUpCmBgYAoKYGBge3J9CnNvbHV0aW9uICU+JSBpbm5lcl9qb2luKG9wdGltX2tpZHNfaW5fYmxrcywgYnk9J0JMSycpICU+JSBpbm5lcl9qb2luKHRyYXZlbF9mcm9tX2Jsa3MsIGJ5PWMoJ0JMSyc9J0JMSycsICdzY2hvb2wnPSdkc3QnKSkgJT4lCiAgZ3JvdXBfYnkoc2Nob29sKSAlPiUgc3VtbWFyaXNlKAogICAga2lkcz1zdW0oa2lkcyksCiAgICBudW1fYmxvY2tzPW4oKSwKICAgIG1pbl9kaXN0PW1pbihtaW4pLAogICAgYXZnX2Rpc3Q9bWVhbigoa2lkcyphdmcpL3N1bShraWRzKSksCiAgICBtYXhfZGlzdD1tYXgobWF4KQogICkgJT4lCiAgaW5uZXJfam9pbihyZWxldmFudF9rYXBhcywgYnk9Yygnc2Nob29sJz0nU2NodWxudW1tZXInKSkgJT4lCiAgbXV0YXRlKAogICAgdXRpbGl6YXRpb249a2lkcy9LYXBhCiAgKSAlPiUgc2VsZWN0KAogICBTY2h1bGU9c2Nob29sLAogICBgQmzDtmNrZWA9bnVtX2Jsb2NrcywKICAgS2FwYXppdMOkdD1LYXBhLAogICBLaW5kZXI9a2lkcywKICAgQXVzbGFzdHVuZz11dGlsaXphdGlvbiwKICAgYFdlZyAobWluKWA9bWluX2Rpc3QsCiAgIGBXZWcgKMOYKWA9YXZnX2Rpc3QsCiAgIGBXZWcgKG1heClgPW1heF9kaXN0CiAgKSAlPiUKICBmb3JtYXR0YWJsZSgKICAgIGxpc3QoCiAgICAgIEtpbmRlciA9IGZvcm1hdHRlcigic3BhbiIsIHggfiBkaWdpdHMoeCwgMikpLAogICAgICBBdXNsYXN0dW5nID0gZm9ybWF0dGVyKCJzcGFuIiwKICAgICAgICBzdHlsZSA9IHggfiBzdHlsZShjb2xvciA9IGlmZWxzZSh4IDwgMSwgImdyZWVuIiwgInJlZCIpKSwKICAgICAgICB4IH4gaWNvbnRleHQoaWZlbHNlKHggPCAxLCAib2siLCAicmVtb3ZlIiksIHBlcmNlbnQoeCkpCiAgICAgICksCiAgICAgIGBXZWcgKMOYKWAgPSBwcm9wb3J0aW9uX2JhcigibGlnaHRibHVlIiksCiAgICAgIGBXZWcgKG1pbilgID0gcHJvcG9ydGlvbl9iYXIoImxpZ2h0Ymx1ZSIpLAogICAgICBgV2VnIChtYXgpYCA9IHByb3BvcnRpb25fYmFyKCJsaWdodGJsdWUiKQogICAgKQogICkKYGBgCgoKIyMgRGF0ZW4gZsO8ciBkaWUgQXBwIHNwZWljaGVybgoKLSBlbnRpdGllcy5nZW9qc29uCi0gZW50aXRpZXMuY3N2Ci0gdW5pdHMuZ2VvanNvbgotIHVuaXRzLmNzdgotIHdlaWdodHMuY3N2Ci0gYXNzaWdubWVudC5jc3YKCiMjIyBOZXVlIERhdGVuCgoKIyMjIyBFbnRpdGllcyAvIFNjaHVsZW4KCmBgYHtyfQplbnRpdGllcyA9IHN1YnNldChyZV9zY2h1bHN0YW5kX2RmLCBzcGF0aWFsX25hbWUgJWluJSByZWxldmFudF9zY2hvb2xzKSAlPiUKICByZW5hbWUoZW50aXR5X2lkID0gc3BhdGlhbF9uYW1lKSAlPiUKICBzZWxlY3QoLWdtbF9pZCwgLXNwYXRpYWxfYWxpYXMsIC1zcGF0aWFsX3R5cGUpICU+JQogIGlubmVyX2pvaW4ocmVuYW1lKHJlbGV2YW50X2thcGFzLCBjYXBhY2l0eT1LYXBhKSwgYnk9YygnZW50aXR5X2lkJz0nU2NodWxudW1tZXInKSkKY29vcmRpbmF0ZXMoZW50aXRpZXMpID0gfiBsb24gKyBsYXQKcHJvajRzdHJpbmcoZW50aXRpZXMpID0gQ1JTKCIrcHJvaj1sb25nbGF0ICtlbGxwcz1XR1M4NCArZGF0dW09V0dTODQgK25vX2RlZnMiKQpmaWxlLnJlbW92ZSgnYXBwL2RhdGEvZW50aXRpZXMuZ2VvanNvbicpCmVudGl0aWVzICU+JSB3cml0ZU9HUignYXBwL2RhdGEvZW50aXRpZXMuZ2VvanNvbicsIGxheWVyPSJlbnRpdGllcyIsIGRyaXZlcj0iR2VvSlNPTiIsIGNoZWNrX2V4aXN0cz1GKQplbnRpdGllc0BkYXRhICU+JSBzZWxlY3QoZW50aXR5X2lkLCBjYXBhY2l0eSkgJT4lIHdyaXRlX2NzdignYXBwL2RhdGEvZW50aXRpZXMuY3N2JykKYGBgCgojIyMjIFVuaXRzIC8gU3RhdGlzdGlzY2hlIEJsw7Zja2UKCmBgYHtyfQp1bml0cyA9IHN1YnNldChibGssIEJFWiA9PSBiZXpfaWQpCgp1bml0c0BkYXRhJFBMUiA9IE5VTEwKdW5pdHNAZGF0YSRFaW53ID0gTlVMTAp1bml0c0BkYXRhJEJFWiA9IE5VTEwKdW5pdHNAZGF0YSR1bml0X2lkID0gdW5pdHNAZGF0YSRCTEsKdW5pdHNAZGF0YSRCTEsgPSBOVUxMCgp1bml0cyA9IHVuaXRzICU+JSBzcDo6bWVyZ2UoCiAgb3B0aW1fa2lkc19pbl9ibGtzICU+JQogICAgbGVmdF9qb2luKHNnYklJX2JsaykgJT4lCiAgICBzZWxlY3QodW5pdF9pZD1CTEssIHBvcHVsYXRpb249a2lkcywgc2diSUl1NjUpCiAgKQoKZmlsZS5yZW1vdmUoJ2FwcC9kYXRhL3VuaXRzLmdlb2pzb24nKQp1bml0cyAlPiUgd3JpdGVPR1IoJ2FwcC9kYXRhL3VuaXRzLmdlb2pzb24nLCBsYXllcj0iZW50aXRpZXMiLCBkcml2ZXI9Ikdlb0pTT04iLCBjaGVja19leGlzdHM9RikKdW5pdHNAZGF0YSAlPiUgd3JpdGVfY3N2KCdhcHAvZGF0YS91bml0cy5jc3YnKQpgYGAKCiMjIyMgWnVvcmRudW5nCgpgYGB7cn0KYXNzaWdubWVudCA9IHNvbHV0aW9uICU+JSBzZWxlY3QodW5pdF9pZD1CTEssIGVudGl0eV9pZD1zY2hvb2wpCmFzc2lnbm1lbnQgJT4lIHdyaXRlX2NzdignYXBwL2RhdGEvYXNzaWdubWVudC5jc3YnKQpgYGAKCiMjIyMgV2VpZ2h0cyAvIFNjaHVsd2VnZQoKYGBge3J9CnRyYXZlbF9mcm9tX2Jsa3MgJT4lCiAgcmVuYW1lKHVuaXRfaWQ9QkxLLCBlbnRpdHlfaWQ9ZHN0KSAlPiUKICAjZ2F0aGVyKHdlaWdodCwgdmFsdWUsIC11bml0X2lkLCAtZW50aXR5X2lkKSAlPiUKICB3cml0ZV9jc3YoJ2FwcC9kYXRhL3dlaWdodHMuY3N2JykKYGBgCgojIyMjIFp1c8OkdHpsaWNoZSBEYXRlbgoKYGBge3J9CmZpbGUuY29weSgnZG93bmxvYWQvUkJTX09EX0JFWl8yMDE1XzEyLmdlb2pzb24nLCAnYXBwL2RhdGEvUkJTX09EX0JFWl8yMDE1XzEyLmdlb2pzb24nKQpgYGAKCg==